package com.chen.gulimall.gulimallproduct.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.chen.gulimall.gulimallproduct.entity.CategoryBrandRelationEntity;
import com.chen.gulimall.gulimallproduct.service.CategoryBrandRelationService;
import com.chen.gulimall.gulimallproduct.vo.Catelog2Vo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.gulimallcommon.common.utils.PageUtils;
import com.chen.gulimallcommon.common.utils.Query;

import com.chen.gulimall.gulimallproduct.dao.CategoryDao;
import com.chen.gulimall.gulimallproduct.entity.CategoryEntity;
import com.chen.gulimall.gulimallproduct.service.CategoryService;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    //此次取出对应的分级索引
    @Override
    public List<CategoryEntity> listWithTree() {

        //先将所有的商品取出来
        List<CategoryEntity> entities = baseMapper.selectList(null);

        //再将取出的商品集合进行filter  集合中的stream（）方法为先转换为流，再filter（）其中的参数为lambol表达式，编写一个函数，
        //当数据项中的父节点为0时表示为第一级结点
        List<CategoryEntity> entitiesTree = entities.stream().filter(category->category.getParentCid() == 0).
                //map（）方法为映射，将每个父节点取出来再设置它的子结点
                map((menu)->{
                    menu.setChildren(getChildren(menu,entities));
                    return menu;
                }).sorted((menu1,menu2)->{
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
            //最后再重新转为集合传给新的树结构集合
        }) .collect(Collectors.toList());

        return entitiesTree;
    }

    //编写自定义的删除方法
    @Override
    public void removeByLoginByIds(List<Long> asList) {

        baseMapper.deleteBatchIds(asList);
    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> list = new ArrayList<>();

        List<Long> paths = findParentPath(catelogId,list);


        Collections.reverse(paths);

        return  paths.toArray(new Long[paths.size()]);
    }

    @Override
    public void updateCasCach(CategoryEntity categoryEntity) {
        this.updateById(categoryEntity);

        if (!StringUtils.isEmpty(categoryEntity.getName())) {
            CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
            categoryBrandRelationEntity.setCatelogId(categoryEntity.getCatId());
            categoryBrandRelationEntity.setCatelogName(categoryEntity.getName());

            categoryBrandRelationService.update(categoryBrandRelationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id",categoryEntity.getCatId()));
        }
    }

    @Override
    public List<CategoryEntity> queryLevel1Categorys() {
        List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid",0));
        return categoryEntities;
    }

    //redis的使用，相对于map作为一个缓存的用法
    @Override
    public Map<String, List<Catelog2Vo>> getCatagorysJson() {
        String catalongs = stringRedisTemplate.opsForValue().get("catalogs");

        if (StringUtils.isEmpty(catalongs)) {
            //缓存中没有，则查询数据库中的信息
            Map<String, List<Catelog2Vo>> jsonFoDataBase = getCatagorysJsonFoDataBase();
            return jsonFoDataBase;
        }
        //将取出的json字符串转为对应的对象
        Map<String, List<Catelog2Vo>> map = JSON.parseObject(catalongs, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
        return map;
    }

    //从数据库中获取所有的category分级商品
    public Map<String, List<Catelog2Vo>> getCatagorysJsonFoDataBase() {
        /**
         * 1.缓存穿透问题，当请求中存在数据库中无效信息时，则会无效请求数据库（将数据库中null的值在redis中设置特殊的值标志）
         * 2.缓存雪崩问题，当redis中的大量数据同一时刻失效时，下一次请求则会向数据库请求大量数据，导致数据库崩溃（尽量设置redis数据失效日期的不同）
         * 3.解决缓存击穿问题（当一个redis数据有效期失效，突然大量请求此数据时，如果不设置锁则将数据库请求多次，使用锁来解决）
         * 本地锁，同一个tomcat的springboot容器下的实例都是单列的，所以可以用this（categoryServiceImpl实例）加密，但是对于分布式应用不适合
         */
        synchronized (this) {
            //先判断redis缓存中有没有放入数据先，在执行此锁

            String catalongs = stringRedisTemplate.opsForValue().get("catalogs");
            //如果redis已经在第一个查询数据库放入数据，则直接返回，不再查询数据库
            if (!StringUtils.isEmpty(catalongs)) {
                Map<String, List<Catelog2Vo>> map = JSON.parseObject(catalongs, new TypeReference<Map<String, List<Catelog2Vo>>>() {});
                return map;
            }

            List<CategoryEntity> categoryEntities = queryLevel1Categorys();
            Map<String, List<Catelog2Vo>> map = categoryEntities.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), v -> {
                List<CategoryEntity> level2Entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v.getCatId()));

                List<Catelog2Vo> catelog2Vos = null;
                if (level2Entities != null) {
                    catelog2Vos = level2Entities.stream().map(l2 -> {
                        Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

                        List<CategoryEntity> level3Entities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", l2.getCatId()));
                        if (level3Entities != null) {
                            List<Catelog2Vo.Catelog3Vo> catelog3Vos = level3Entities.stream().map(l3 -> {
                                Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
                                return catelog3Vo;
                            }).collect(Collectors.toList());
                            catelog2Vo.setCatalog3List(catelog3Vos);
                        }
                        return catelog2Vo;
                    }).collect(Collectors.toList());
                }

                return catelog2Vos;
            }));

            //防止在更新操作时第二个进程进来，所以要在锁里执行保存redis数据操作
            String json = JSON.toJSONString(map);
            //然后转为json字符串再次放入redis缓存中，方便下次存取
            stringRedisTemplate.opsForValue().set("catalogs",json);

            return map;
        }
    }




    public List<Long> findParentPath(Long catelogId, List<Long> paths) {
        CategoryEntity categoryEntity = this.getById(catelogId);
        paths.add(catelogId);
        if (categoryEntity.getParentCid() != 0) {
            findParentPath(categoryEntity.getParentCid(),paths);
        }

        return paths;
    }

    //此方法采用DFS方法，递归找到它的对应子结点
    public List<CategoryEntity> getChildren(CategoryEntity entity,List<CategoryEntity> all) {
        List<CategoryEntity> children = all.stream().filter(category->category.getParentCid() == entity.getCatId()).
                map((menu)->{
                    //找到每个子索引的下一个子索引
                    menu.setChildren(getChildren(menu,all));
                    return menu;
                }).sorted((menu1,menu2)->{
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }) .collect(Collectors.toList());

        //返回子结点
        return children;
    }

}